#include "jsco.hpp"

void initJSC() {
  JSContextGroupRef group = JSContextGroupCreate();
  globalJSContext = JSGlobalContextCreateInGroup(group, NULL);
  globalThis = JSContextGetGlobalObject(globalJSContext);
}

void loadScript() {
  const char *script = "var factorial = function (n) {\
                            if (n < 0) return;\
                            if (n == 0) return 1;\
                            return n * factorial(n-1);\
                          };\
                          var Person = function () {\
                            this.age = 18;\
                            this.sayHello = function(name) {\
                                return \"hello \" + name;\
                            }\
                          };\
                          var myName = \"fuyou\";\
                          var p = new Person();";
  JSStringRef scriptStrRef = JSStringCreateWithUTF8CString(script);

  JSEvaluateScript(globalJSContext, scriptStrRef, NULL, NULL, 1, NULL);
  JSStringRelease(scriptStrRef);
}

void readGlobalVariable() {
  JSStringRef myName = JSStringCreateWithUTF8CString("myName");
  JSValueRef myNameValue =
      JSObjectGetProperty(globalJSContext, globalThis, myName, NULL);
  string name = jsValueToStdString(globalJSContext, myNameValue, 30);
  cout << "myName = " << name << endl << endl;
}

void executeGlobalFunction() {
  JSStringRef factorialStr = JSStringCreateWithUTF8CString("factorial");
  JSValueRef factorialValue =
      JSObjectGetProperty(globalJSContext, globalThis, factorialStr, NULL);
  JSObjectRef factorialObj =
      JSValueToObject(globalJSContext, factorialValue, NULL);
  JSValueRef argument = JSValueMakeNumber(globalJSContext, 3);
  JSValueRef arguments[1];
  arguments[0] = argument;
  JSValueRef factorialResultValue = JSObjectCallAsFunction(
      globalJSContext, factorialObj, NULL, 1, arguments, NULL);
  double factorialResult =
      JSValueToNumber(globalJSContext, factorialResultValue, NULL);
  cout << "factorialResult = " << factorialResult << endl << endl;
}

void executeObjectFunction() {
  JSStringRef pStr = JSStringCreateWithUTF8CString("p");
  JSValueRef pStrVal =
      JSObjectGetProperty(globalJSContext, globalThis, pStr, NULL);
  JSStringRelease(pStr);
  JSObjectRef pObj = JSValueToObject(globalJSContext, pStrVal, NULL);
  JSStringRef pFuncStr = JSStringCreateWithUTF8CString("sayHello");
  JSValueRef pFuncVal =
      JSObjectGetProperty(globalJSContext, pObj, pFuncStr, NULL);
  JSStringRelease(pFuncStr);
  JSObjectRef pFuncObj = JSValueToObject(globalJSContext, pFuncVal, NULL);
  JSStringRef argStr = JSStringCreateWithUTF8CString("humphrey");
  JSValueRef arg = JSValueMakeString(globalJSContext, argStr);
  JSStringRelease(argStr);
  JSValueRef args[1];
  args[0] = arg;
  JSValueRef pResVal =
      JSObjectCallAsFunction(globalJSContext, pFuncObj, NULL, 1, args, NULL);
  string pResStr = jsValueToStdString(globalJSContext, pResVal, 30);
  cout << "sayHello() = " << pResStr << endl << endl;
}

string jsValueToStdString(JSGlobalContextRef &context, JSValueRef &jsValue,
                          const CFIndex size) {
  JSStringRef jsStr = JSValueToStringCopy(context, jsValue, NULL);
  CFStringRef cfStr = JSStringCopyCFString(kCFAllocatorSystemDefault, jsStr);
  char charArr[size];
  bzero(charArr, size);
  CFStringGetCString(cfStr, charArr, size, kCFStringEncodingUTF8);
  string name(charArr);
  JSStringRelease(jsStr);
  return name;
}

JSValueRef getProperty(JSContextRef ctx, JSObjectRef object,
                       JSStringRef propertyName, JSValueRef *exception) {
  Worker *w = static_cast<Worker *>(JSObjectGetPrivate(object));
  return JSValueMakeNumber(ctx, w->money);
}
JSValueRef callAsFunction(JSContextRef ctx, JSObjectRef function,
                          JSObjectRef thisObject, size_t argumentCount,
                          const JSValueRef arguments[], JSValueRef *exception) {
  Worker *w = static_cast<Worker *>(JSObjectGetPrivate(thisObject));
  w->work();
  return JSValueMakeUndefined(ctx);
}

void jsInvoekNative() {
  JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
  JSObjectRef gloableThis = JSContextGetGlobalObject(ctx);
  JSStaticValue values[] = {{"money", &getProperty, 0, kJSClassAttributeNone},
                            {0, 0, 0, 0}};
  JSStaticFunction functions[] = {
      {"work", &callAsFunction, kJSPropertyAttributeNone}, {0, 0, 0}};

  JSClassDefinition classDef = kJSClassDefinitionEmpty;
  classDef.version = 0;
  classDef.attributes = kJSClassAttributeNone;
  classDef.className = "Worker";
  classDef.parentClass = 0;
  classDef.staticValues = values;
  classDef.staticFunctions = functions;
  JSClassRef t = JSClassCreate(&classDef);
  Worker w;
  JSObjectRef classObj = JSObjectMake(ctx, t, &w);

  JSStringRef objName = JSStringCreateWithUTF8CString("w");
  JSObjectSetProperty(ctx, gloableThis, objName, classObj,
                      kJSClassAttributeNone, NULL);

  JSStringRef workScript = JSStringCreateWithUTF8CString("w.work();");
  JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
  JSEvaluateScript(ctx, workScript, classObj, NULL, 1, NULL);
  JSStringRelease(workScript);

  JSStringRef getMoneyScript =
      JSStringCreateWithUTF8CString("var money = w.money;");
  JSEvaluateScript(ctx, getMoneyScript, classObj, NULL, 1, NULL);
  JSStringRelease(getMoneyScript);

  JSStringRef moneyRef = JSStringCreateWithUTF8CString("money");
  JSValueRef moneyValue = JSObjectGetProperty(ctx, gloableThis, moneyRef, NULL);
  JSStringRelease(moneyRef);
  double money = JSValueToNumber(ctx, moneyValue, NULL);
  cout << endl << "JS环境中， w.money = " << money << endl;
  cout << "原生环境中，w.money = " << w.money << endl << endl;

  JSGlobalContextRelease(ctx);
}

int main(int argc, char **argv) {
  initJSC();
  loadScript();
  readGlobalVariable();
  executeGlobalFunction();
  executeObjectFunction();
  jsInvoekNative();
  return 0;
}
